Aller au contenu principal

Symfony UX Native

Créer des applications mobiles avec PHP et Symfony

Notions théoriques

Qu'est-ce que Symfony UX Native ?

Symfony UX Native est une initiative expérimentale qui étend la philosophie de Symfony UX au-delà du navigateur web, vers le monde des applications mobiles natives. L'idée centrale est de permettre à un développeur PHP de créer des applications iOS et Android en utilisant ses connaissances Symfony existantes, sans avoir à apprendre Swift, Kotlin, React Native ou Flutter.

info

Symfony UX Native s'appuie sur NativePHP, un projet open source qui permet d'écrire des applications de bureau et mobiles en PHP. Symfony UX Native est l'intégration officielle de NativePHP dans l'écosystème Symfony UX.

Le projet repose sur une idée provocatrice mais cohérente : si Symfony UX permet déjà de faire du frontend interactif depuis PHP, pourquoi ne pas aller encore plus loin et cibler les plateformes mobiles directement ?

NativePHP : la technologie sous-jacente

Pour comprendre Symfony UX Native, il faut d'abord comprendre NativePHP.

NativePHP est un framework PHP créé par Simon Hamp en 2023. Il permet d'exécuter des applications PHP comme des applications de bureau ou mobiles natives, en embarquant un serveur PHP directement dans le binaire de l'application. Concrètement, NativePHP utilise deux moteurs selon la cible :

  • Electron (pour les applications de bureau Windows, macOS, Linux)
  • Capacitor (pour les applications mobiles iOS et Android)

Le principe est le suivant : au lieu d'exécuter PHP sur un serveur distant, NativePHP exécute PHP localement sur l'appareil de l'utilisateur. L'interface graphique est rendue par un moteur WebView natif (le même moteur que le navigateur intégré au système).

astuce

Le modèle de NativePHP ressemble à celui d'Electron (qui fait tourner Node.js localement pour des apps comme VS Code ou Slack), mais avec PHP au lieu de JavaScript.

L'architecture de Symfony UX Native

Symfony UX Native s'articule autour de 3 couches :

1. Le backend Symfony (PHP) C'est ici que vit toute la logique applicative : contrôleurs, entités Doctrine, services, etc. Ce code PHP s'exécute localement sur l'appareil mobile, via un serveur PHP embarqué.

2. Les vues Twig + Symfony UX L'interface utilisateur est construite avec Twig et les composants Symfony UX habituels (Twig Components, Live Components, Turbo). Le rendu HTML est affiché dans la WebView native de l'appareil.

3. Le pont natif (Native Bridge) C'est la couche qui permet à PHP d'accéder aux fonctionnalités natives de l'appareil : caméra, GPS, notifications push, vibrations, stockage local, accéléromètre, etc.

Application mobile
├── WebView (rendu HTML/CSS)
│ ├── Templates Twig
│ └── Composants Symfony UX
├── Serveur PHP embarqué
│ ├── Contrôleurs Symfony
│ ├── Services
│ └── Doctrine (SQLite local)
└── Native Bridge
├── Caméra
├── GPS
├── Notifications
└── Capteurs
remarque

La base de données utilisée dans une application Symfony UX Native est généralement SQLite plutôt que MariaDB, car elle ne nécessite pas de serveur séparé et s'embarque directement dans l'application.

Les composants spécifiques à Symfony UX Native

Symfony UX Native introduit plusieurs composants PHP dédiés aux fonctionnalités mobiles :

NativeCamera Permet d'accéder à la caméra de l'appareil pour prendre des photos ou scanner des QR codes.

use Symfony\UX\Native\Component\NativeCamera;

#[AsTwigComponent]
class PhotoCapture
{
public function takePhoto(): NativeCamera
{
return new NativeCamera(quality: 80, allowGallery: true);
}
}

NativeGeolocation Fournit la position GPS de l'appareil en temps réel.

use Symfony\UX\Native\Component\NativeGeolocation;

$location = NativeGeolocation::getCurrentPosition();
echo $location->latitude . ', ' . $location->longitude;

NativeNotification Envoie des notifications push locales, même lorsque l'application est en arrière-plan.

use Symfony\UX\Native\Notification\NativeNotification;

$notification = new NativeNotification(
title: 'Rappel',
body: 'Votre rendez-vous commence dans 15 minutes.',
badge: 1
);
$notification->schedule(delay: 900); // 15 minutes

NativeStorage Permet de stocker des données persistantes localement sur l'appareil, en dehors de la base de données.

use Symfony\UX\Native\Storage\NativeStorage;

NativeStorage::set('theme', 'dark');
$theme = NativeStorage::get('theme'); // 'dark'
attention

Symfony UX Native est un projet expérimental au moment de la rédaction de ce cours. L'API peut évoluer entre les versions. Consultez toujours la documentation officielle avant de commencer un projet en production : https://native.symfony.com

Le cycle de développement d'une application Symfony UX Native

Le développement d'une application Symfony UX Native suit un cycle proche de celui d'une application web Symfony classique, avec quelques étapes supplémentaires :

Phase 1 — Développement web On développe et teste l'application dans le navigateur, exactement comme une application Symfony classique. C'est la phase la plus longue et la plus confortable pour un développeur PHP.

Phase 2 — Intégration native On ajoute les fonctionnalités natives (caméra, GPS, notifications) via les composants Native*. Ces composants se dégradent gracieusement dans le navigateur (ils affichent une interface de simulation).

Phase 3 — Build mobile On compile l'application pour iOS et/ou Android via les commandes CLI de Symfony UX Native. Cette étape nécessite Xcode (macOS, pour iOS) ou Android Studio (pour Android).

Phase 4 — Distribution L'application est publiée sur l'App Store ou le Google Play Store comme n'importe quelle application native.

info

Pour le développement sur Windows, seul le build Android est possible nativement. Le build iOS nécessite un Mac avec Xcode. Des services de build cloud comme Bitrise ou GitHub Actions permettent de contourner cette limitation.

Comparaison avec les alternatives

CritèreSymfony UX NativeReact NativeFlutterIonic
Langage principalPHPJavaScriptDartJavaScript
Courbe apprentissage (dev PHP)FaibleÉlevéeTrès élevéeMoyenne
Performances UIMoyenne (WebView)Élevée (natif)Très élevée (natif)Moyenne (WebView)
Accès APIs nativesVia Native BridgeDirectDirectVia Capacitor
MaturitéExpérimentalMatureMatureMature
Partage code web/mobileTrès élevéMoyenFaibleÉlevé

Les Live Components dans une application mobile

L'un des atouts majeurs de Symfony UX Native est la compatibilité totale avec les Live Components. Un formulaire de recherche avec filtre en temps réel, un panier d'achat réactif, ou un tableau de bord mis à jour automatiquement — tous ces éléments fonctionnent dans une application mobile Symfony UX Native exactement comme dans une application web.

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveProp;

#[AsLiveComponent]
class SearchProduct
{
#[LiveProp(writable: true)]
public string $query = '';

public function getResults(): array
{
// Cette requête Doctrine s'exécute localement sur l'appareil
return $this->productRepository->search($this->query);
}
}
astuce

La réactivité des Live Components dans Symfony UX Native ne passe pas par le réseau : tout s'exécute localement sur l'appareil. Les performances sont donc excellentes même sans connexion internet.


Exemple pratique

Création d'une application mobile de liste de tâches

Cet exemple montre comment créer une application mobile simple avec Symfony UX Native : une liste de tâches (todo list) avec ajout, suppression et stockage local.

info

Pour exécuter cet exemple, Symfony UX Native doit être installé. Le projet de démonstration officiel est disponible sur https://github.com/symfony/ux-native-demo. Si ce dépôt n'est pas disponible, suivre les étapes de création manuelle ci-dessous.

Étape 1 — Créer le projet

cd %USERPROFILE%\Documents
symfony new tp-native-todo --webapp
cd tp-native-todo
composer require symfony/ux-native
code .

Étape 2 — Créer l'entité Tache

php bin/console make:entity Tache

Ajouter les champs :

  • titre : string, longueur 255, non nullable
  • terminee : boolean, valeur par défaut false
  • creeLe : datetime_immutable, non nullable

Étape 3 — Créer la migration et la base de données

Modifier .env pour utiliser SQLite :

DATABASE_URL="sqlite:///%kernel.project_dir%/var/todo.db"

Puis créer la base de données et les tables :

php bin/console doctrine:database:create
php bin/console make:migration
php bin/console doctrine:migrations:migrate

Étape 4 — Créer le Live Component TodoList

Créer src/Twig/Components/TodoList.php :

<?php

namespace App\Twig\Components;

use App\Entity\Tache;
use App\Repository\TacheRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class TodoList
{
use DefaultActionTrait;

#[LiveProp(writable: true)]
public string $nouvelleTache = '';

public function __construct(
private TacheRepository $tacheRepository,
private EntityManagerInterface $em
) {}

public function getTaches(): array
{
return $this->tacheRepository->findBy([], ['creeLe' => 'DESC']);
}

#[LiveAction]
public function ajouter(): void
{
if (trim($this->nouvelleTache) === '') {
return;
}

$tache = new Tache();
$tache->setTitre($this->nouvelleTache);
$tache->setTerminee(false);
$tache->setCreeLe(new \DateTimeImmutable());

$this->em->persist($tache);
$this->em->flush();

$this->nouvelleTache = '';
}

#[LiveAction]
public function basculer(int $id): void
{
$tache = $this->tacheRepository->find($id);
if ($tache) {
$tache->setTerminee(!$tache->isTerminee());
$this->em->flush();
}
}

#[LiveAction]
public function supprimer(int $id): void
{
$tache = $this->tacheRepository->find($id);
if ($tache) {
$this->em->remove($tache);
$this->em->flush();
}
}
}

Étape 5 — Créer le template du composant

Créer templates/components/TodoList.html.twig :

<div {{ attributes }}>
<h2>Ma liste de tâches</h2>

<div style="display: flex; gap: 0.5rem; margin-bottom: 1.5rem;">
<input
type="text"
data-model="nouvelleTache"
placeholder="Nouvelle tâche..."
style="flex: 1; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px;"
>
<button
data-action="live#action"
data-live-action-param="ajouter"
style="padding: 0.5rem 1rem; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;"
>
Ajouter
</button>
</div>

{% for tache in this.getTaches() %}
<div style="display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem; border-bottom: 1px solid #eee;">
<input
type="checkbox"
{{ tache.terminee ? 'checked' : '' }}
data-action="live#action"
data-live-action-param="basculer"
data-live-id-param="{{ tache.id }}"
style="width: 1.2rem; height: 1.2rem; cursor: pointer;"
>
<span style="{{ tache.terminee ? 'text-decoration: line-through; color: #999;' : '' }} flex: 1;">
{{ tache.titre }}
</span>
<button
data-action="live#action"
data-live-action-param="supprimer"
data-live-id-param="{{ tache.id }}"
style="background: none; border: none; color: #e53e3e; cursor: pointer; font-size: 1.2rem;"
>
&times;
</button>
</div>
{% else %}
<p style="color: #999; text-align: center; padding: 2rem 0;">Aucune tâche pour le moment.</p>
{% endfor %}
</div>

Étape 6 — Créer la page principale

php bin/console make:controller TodoController

Modifier src/Controller/TodoController.php :

#[Route('/todo', name: 'app_todo')]
public function index(): Response
{
return $this->render('todo/index.html.twig');
}

Modifier templates/todo/index.html.twig :

{% extends 'base.html.twig' %}

{% block title %}Todo List — Symfony UX Native{% endblock %}

{% block body %}
<div style="max-width: 500px; margin: 2rem auto; padding: 0 1rem;">
<h1>Application Todo</h1>
<twig:TodoList />
</div>
{% endblock %}

Étape 7 — Tester en mode web

symfony server:start

Accéder à https://127.0.0.1:8000/todo. L'application fonctionne dans le navigateur : ajout de tâches, cocher/décocher, suppression — tout en temps réel sans rechargement de page grâce aux Live Components.

astuce

Cette phase de développement en mode navigateur est la plus importante. Une fois l'application fonctionnelle en mode web, le passage au mode mobile avec Symfony UX Native ne nécessite que peu de modifications supplémentaires.


Test de mémorisation/compréhension


Sur quelle technologie Symfony UX Native s'appuie-t-il pour les applications mobiles ?


Quel moteur NativePHP utilise-t-il pour les applications mobiles iOS et Android ?


Quelle base de données est recommandée dans une application Symfony UX Native mobile ?


Quel composant Symfony UX Native permet d'envoyer des notifications locales à l'appareil ?


Quel est l'avantage principal des Live Components dans une application Symfony UX Native ?


Sur Windows, quel build mobile est possible nativement sans Mac ?


Quel attribut PHP transforme une classe en Live Component dans Symfony UX ?


Quel attribut PHP marque une méthode de Live Component comme déclenchable depuis Twig ?


Comment qualifier le statut de Symfony UX Native au moment de la rédaction de ce cours ?


Quelle couche de Symfony UX Native permet d'accéder au GPS ou à la caméra de l'appareil ?



TP pour réfléchir et résoudre des problèmes

Dans ce TP, vous allez créer une application de gestion de notes personnelles avec Symfony UX Native. L'application permettra d'ajouter, de modifier le statut et de supprimer des notes, stockées dans une base SQLite locale. Toute l'interactivité sera gérée via un Live Component, sans rechargement de page.


Étape 1 — Créer le projet et configurer SQLite

Ouvrez un terminal PowerShell ou le terminal intégré de VS Code (Ctrl + ù), naviguez vers votre dossier Documents et créez le projet :

cd %USERPROFILE%\Documents
symfony new tp-notes-native --webapp
cd tp-notes-native
code .

Installez ensuite les packages nécessaires pour les Live Components :

composer require symfony/ux-live-component
composer require symfony/ux-twig-component

Puis configurez la base de données SQLite en ouvrant le fichier .env et en remplaçant la ligne DATABASE_URL par :

DATABASE_URL="sqlite:///%kernel.project_dir%/var/notes.db"
Une solution

Étape 2 — Créer l'entité Note

Générez l'entité Note via la commande MakerBundle :

php bin/console make:entity Note

Ajoutez les champs suivants lors de l'invite interactive :

  • titre : type string, longueur 255, non nullable
  • contenu : type text, non nullable
  • importante : type boolean, valeur par défaut false
  • creeLe : type datetime_immutable, non nullable

Une fois l'entité créée, générez la migration et exécutez-la :

php bin/console make:migration
php bin/console doctrine:migrations:migrate
Une solution

Étape 3 — Créer le Live Component NoteManager

Créez le fichier src/Twig/Components/NoteManager.php. Ce composant doit gérer l'intégralité des interactions avec les notes : affichage de la liste, ajout d'une nouvelle note, basculement du statut "importante", et suppression.

Le composant doit avoir :

  • Une propriété #[LiveProp(writable: true)] $nouveauTitre de type string
  • Une propriété #[LiveProp(writable: true)] $nouveauContenu de type string
  • Une méthode getNotes() qui retourne toutes les notes triées par date décroissante
  • Trois méthodes #[LiveAction] : ajouter(), basculerImportance(int $id), supprimer(int $id)
Une solution

Étape 4 — Créer le template du Live Component

Créez le fichier templates/components/NoteManager.html.twig. Ce template doit afficher :

  • Un formulaire d'ajout avec deux champs (nouveauTitre et nouveauContenu) et un bouton d'action ajouter
  • La liste des notes existantes, chacune avec son titre, son contenu, un bouton pour marquer comme importante (avec un style visuel différent si importante est vrai), et un bouton de suppression
  • Un message "Aucune note" si la liste est vide
Une solution

Étape 5 — Créer le contrôleur et la page principale

Générez un contrôleur nommé NotesController :

php bin/console make:controller NotesController

Modifiez src/Controller/NotesController.php pour que la route /notes retourne simplement le template notes/index.html.twig sans passer de variables (le composant gère tout lui-même).

Modifiez ensuite templates/notes/index.html.twig pour appeler le composant <twig:NoteManager />.

Une solution

Étape 6 — Vérifier la configuration et tester l'application

Vérifiez que {{ importmap('app') }} est présent dans templates/base.html.twig, puis lancez le serveur :

symfony server:start

Accédez à https://127.0.0.1:8000/notes et testez les fonctionnalités suivantes :

  1. Ajouter une note avec un titre et un contenu — les champs doivent se vider automatiquement après ajout
  2. Ajouter une note sans titre — rien ne doit se passer (validation silencieuse)
  3. Cliquer sur l'étoile d'une note — la bordure et le fond doivent devenir jaunes et le badge "Importante" doit apparaître
  4. Recliquer sur l'étoile — la note doit revenir à son état normal
  5. Supprimer une note — elle doit disparaître immédiatement sans rechargement de page
  6. Vérifier dans la console du navigateur (F12) l'absence d'erreurs JavaScript
Une solution